#include "degfx/degfx.h"
#include <string.h>

#define MX__SWAP(a,b)           \
    do {                    \
        const int temp = a; \
        a = b;              \
        b = temp;           \
    } while (0)

#define MX__GETCODE(x,y,code)     \
    do {                          \
        if (x < bitmap->_clip.x1) \
            code |= 1;            \
        if (x > bitmap->_clip.x2) \
            code |= 2;            \
        if (y < bitmap->_clip.y1) \
            code |= 4;            \
        if (y > bitmap->_clip.y2) \
            code |= 8;            \
    } while (0)

void mx_bitmap_line(MX_BITMAP * bitmap, int x1, int y1, int x2, int y2, MX_PIXEL color)
{
	int code1 = 0, code2 = 0;
	int dy = abs(y2 - y1) << 1;
	int dx = abs(x2 - x1) << 1;
	MX_PIXEL *iter;

	const unsigned int trans = MXT(color);

	if (trans == 0xFF)
		return;

	MX__GETCODE(x1, y1, code1);
	MX__GETCODE(x2, y2, code2);

	if (code1 & code2)
		return;

	if (!MXRECT_VALID(bitmap->_clip))
		return;

	if (dx == 0) {
		if (y2 < y1) {
			MX__SWAP(x1, x2);
			MX__SWAP(y1, y2);
		}

		mx_bitmap_vline(bitmap, x1, y1, y2, color);

	} else if (dy == 0) {
		if (x2 < x1) {
			MX__SWAP(x1, x2);
			MX__SWAP(y1, y2);
		}

		mx_bitmap_hline(bitmap, x1, y1, x2, color);

	} else if (dx > dy) {
		int stepy, stopy;
		int fraction = dy - (dx >> 1);

		if (x2 < x1) {
			MX__SWAP(x1, x2);
			MX__SWAP(y1, y2);
		}

		if (y2 > y1) {
			stepy = 1;
			stopy = bitmap->_clip.y2 + 1;
		} else {
			stepy = -1;
			stopy = bitmap->_clip.y1 - 1;
		}

		if (x1 < bitmap->_clip.x1) {
			const int xn = bitmap->_clip.x1 - x1;
			const int cy = (fraction + xn * dy - dy + dx) / dx;

			x1 = bitmap->_clip.x1;
			y1 = y1 + cy * stepy;
			fraction = fraction - cy * dx + xn * dy;
		}

		x2 = MX_MIN(x2, bitmap->_clip.x2);
		if (x1 > x2)
			return;

		if (((stepy == 1) && (y1 > bitmap->_clip.y2))
			|| ((stepy == -1) && (y1 < bitmap->_clip.y1)))
			return;

		if (y2 == y1) {
			mx_bitmap_hline(bitmap, x1, y1, x2, color);
			return;

		} else if ((stepy == 1) && (y1 < bitmap->_clip.y1)) {
			const int yn = bitmap->_clip.y1 - y1;
			const int cx = (dy - dx - fraction + yn * dx + dy - 1) / dy;

			x1 = x1 + cx;
			y1 = bitmap->_clip.y1;
			fraction = fraction - yn * dx + cx * dy;

		} else if ((stepy == -1) && (y1 > bitmap->_clip.y2)) {
			const int yn = y1 - bitmap->_clip.y2;
			const int cx = (dy - dx - fraction + yn * dx + dy - 1) / dy;

			x1 = x1 + cx;
			y1 = bitmap->_clip.y2;
			fraction = fraction - yn * dx + cx * dy;
		}

		iter = mx_bitmap_iter(bitmap, x1, y1);

		if (trans) {
			while ((x1 <= x2) && (y1 != stopy)) {
				*iter = MXBLENDT(color, *iter, trans);

				if (fraction >= 0) {
					y1 += stepy;
					iter += bitmap->_pitch * stepy;
					fraction -= dx;
				}
				++x1;
				++iter;
				fraction += dy;
			}
		} else {
			while ((x1 <= x2) && (y1 != stopy)) {
				*iter = color;

				if (fraction >= 0) {
					y1 += stepy;
					iter += bitmap->_pitch * stepy;
					fraction -= dx;
				}
				++x1;
				++iter;
				fraction += dy;
			}
		}
	} else {
		int stepx, stopx;
		int fraction = dx - (dy >> 1);

		if (y2 < y1) {
			MX__SWAP(y1, y2);
			MX__SWAP(x1, x2);
		}

		if (x2 > x1) {
			stepx = 1;
			stopx = bitmap->_clip.x2 + 1;
		} else {
			stepx = -1;
			stopx = bitmap->_clip.x1 - 1;
		}

		if (y1 < bitmap->_clip.y1) {
			const int yn = bitmap->_clip.y1 - y1;
			const int cx = (fraction + yn * dx - dx + dy) / dy;

			y1 = bitmap->_clip.y1;
			x1 = x1 + cx * stepx;
			fraction = fraction - cx * dy + yn * dx;
		}

		y2 = MX_MIN(y2, bitmap->_clip.y2);
		if (y1 > y2)
			return;

		if (((stepx == 1) && (x1 > bitmap->_clip.x2))
			|| ((stepx == -1) && (x1 < bitmap->_clip.x1)))
			return;

		if (x2 == x1) {
			mx_bitmap_vline(bitmap, x1, y1, y2, color);
			return;

		} else if ((stepx == 1) && (x1 < bitmap->_clip.x1)) {
			const int xn = bitmap->_clip.x1 - x1;
			const int cy = (dx - dy - fraction + xn * dy + dx - 1) / dx;

			y1 = y1 + cy;
			x1 = bitmap->_clip.x1;
			fraction = fraction - xn * dy + cy * dx;

		} else if ((stepx == -1) && (x1 > bitmap->_clip.x2)) {
			const int xn = x1 - bitmap->_clip.x2;
			const int cy = (dx - dy - fraction + xn * dy + dx - 1) / dx;

			y1 = y1 + cy;
			x1 = bitmap->_clip.x2;
			fraction = fraction - xn * dy + cy * dx;
		}

		iter = mx_bitmap_iter(bitmap, x1, y1);

		if (trans) {
			while ((y1 <= y2) && (x1 != stopx)) {
				*iter = MXBLENDT(color, *iter, trans);

				if (fraction >= 0) {
					x1 += stepx;
					iter += stepx;
					fraction -= dy;
				}
				++y1;
				iter += bitmap->_pitch;
				fraction += dx;
			}
		} else {
			while ((y1 <= y2) && (x1 != stopx)) {
				*iter = color;

				if (fraction >= 0) {
					x1 += stepx;
					iter += stepx;
					fraction -= dy;
				}
				++y1;
				iter += bitmap->_pitch;
				fraction += dx;
			}
		}
	}
}
